package com.link.base.base.urlinfo.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.link.base.user.model.CoreUser;
import com.link.base.base.gateway.service.GatewayService;
import com.link.base.base.urlinfo.dao.mybatis.mapper.UrlInfoMapper;
import com.link.base.base.urlinfo.model.UrlEncrypt;
import com.link.base.base.urlinfo.model.UrlInfo;
import com.link.core.basic.properties.CommonProperties;
import com.link.core.cllog.LCLogger;
import com.link.core.basic.dao.mapper.BasicMapper;
import com.link.core.basic.interceptor.SecurityInterceptor;
import com.link.core.basic.service.ServiceException;
import com.link.core.basic.service.BasicServiceImpl;
import com.link.core.basic.service.KeyGenerateService;
import com.link.core.util.PasswordUtil;
import com.link.core.util.RedisUtil;
import com.link.core.util.StringUtils;
import com.link.core.util.UserUtil;
import com.link.core.util.redisclient.LinkRedisClient;
import org.apache.commons.codec.binary.Base64;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import java.util.*;

/**
 * url信息 service实现类
 *
 * @author 黄龙建
 * @date 2020/1/8 5:19 下午
 */
@Service
public class UrlInfoServiceImpl extends BasicServiceImpl<UrlInfo> implements UrlInfoService {

    private static final Logger logger = LogManager.getLogger(UrlInfoServiceImpl.class);

    @Resource
    private GatewayService gatewayService;

    /**
     * 排除的模块
     */
    public static final String EXCLUDSERERNAMES = "LINK-GATEWAY,LINK-CONFIG"
            + ",LINK-EUREKA,LINK-AUTH,LINK-BASE,LINK-LOG,LINK-WEBSOCKET,LINK-LOG,LINK-CANAL";

    @Resource
    private UrlInfoMapper urlInfoMapper;

    @Resource
    private KeyGenerateService keyGenerateService;

    @Override
    public BasicMapper<UrlInfo> getBasicMapper() {
        return urlInfoMapper;
    }

    @Resource
    private RestTemplate restTemplate;

    @Resource
    private DiscoveryClient client;

    @Resource
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    /**
     * @author HuangLJ
     * @date 2019-02-25 17:11
     * @param
     * @return
     * @throws Exception 报错
     * 加载默认的可访问地址到缓存中。同时更新全局变量的版本号
     * （因为是把默认可访问的地址放到全局变量中的，所以在负载时候会出现不统一的问题。通过把版本号存入redis中，实现服务间的统一）
     */
    @Override
    public void loadUrlToRedis() throws Exception {
        LinkRedisClient jedis = RedisUtil.getJedis();
        try {
            //首先设置缓存
            setDefUrlCache();
            gatewayService.refreshAllCache("url");
        } catch (Exception e) {
            LCLogger.withException(e);
            throw e;
        } finally {
            RedisUtil.returnResource(jedis);
        }
    }

    /**
     * @author HuangLJ
     * @date 2019-02-25 17:10
     * 把url信息写入到redis里面去
     */
    @Override
    public void setDefUrlCache() throws Exception {
        LinkRedisClient linkRedisClient = null;
        try {
            linkRedisClient = RedisUtil.getJedis();
            UrlInfo example = new UrlInfo();
            example.setPageFlag(false);
            List<UrlInfo> urlInfoList = urlInfoMapper.queryByExamplePage(example);
            Map<String, String> mapAll = new HashMap<>(3000);
            for (UrlInfo urlInfo : urlInfoList) {
                if (StringUtils.isNotBlank(urlInfo.getUrl())) {
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("url", urlInfo.getUrl());
                    jsonObject.put("delFlag", urlInfo.getDelFlag());
                    jsonObject.put("defaultAccessFlag", urlInfo.getDefaultAccessFlag());
                    jsonObject.put("scopePermission", urlInfo.getScopePermission());
                    jsonObject.put("inputParaFlag", urlInfo.getInputParaFlag());
                    jsonObject.put("outParaFlag", urlInfo.getOutParaFlag());
                    jsonObject.put("logKeepTime", urlInfo.getLogKeepTime());
                    jsonObject.put("blackIps",urlInfo.getBlackList());
                    jsonObject.put("whiteIps",urlInfo.getWhiteList());
                    mapAll.put(urlInfo.getUrl(), JSON.toJSONString(jsonObject));
                }
            }
            //将所有存了url信息的map放到redis中去，供其他地方使用
            linkRedisClient.hmset(CommonProperties.ALL_URL_REDIS_KEY, mapAll);
            logger.info("url信息设置完成");
        } catch (Exception e) {
            throw new Exception(e);
        } finally {
            RedisUtil.returnResource(linkRedisClient);
        }
    }

    @Override
    public List<UrlInfo> listInUrlInfo(List<String> list) throws Exception {
        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }
        return urlInfoMapper.listInUrlInfo(list);
    }

    @Override
    public void updateUrl() throws Exception {
        try {
            Map<String,Object> result = new HashMap<>(5);
            //设置请求头
            HttpHeaders requestHeaders = new HttpHeaders();
            requestHeaders.add("INNERTOKEN", SecurityInterceptor.internal_token.get());

            Map<String, UrlInfo> mapInfoList = new HashMap<>();
            //获取服务列表，向每个服务发送更新url的请求
            List<String> serviceList = client.getServices();
            for (String server : serviceList) {
                mapInfoList.clear();
                if (EXCLUDSERERNAMES.contains(server.toUpperCase())) {
                    continue;
                }
                MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
                requestBody.add("moduleName", server);
                HttpEntity<MultiValueMap> requestEntity = new HttpEntity<>(requestBody, requestHeaders);
                Map<String, Object> response =
                        restTemplate.postForObject("http://" + server + "/common/mappped/getUrlMapping", requestEntity, Map.class);
                if (response == null) {
                    throw new Exception("发生如下错误： 返回的respose为空！");
                } else if (!(boolean) response.get("success")) {
                    throw new Exception(server + "发生如下错误：" + response.get("result"));
                }
                //获取result信息
                List<String> urlList = (List<String>) response.get("result");
                for (String urlMsg : urlList) {
                    String [] urlMsgArr = urlMsg.split("\\|");
                    UrlInfo urlInfo = new UrlInfo();
                    urlInfo.setUrl(urlMsgArr[0]);
                    urlInfo.setName(urlMsgArr[1]);
                    urlInfo.setModuleName(server);
                    mapInfoList.put(urlMsgArr[0], urlInfo);
                }
                //得到一个模块的url列表
                //调用方法更新到url表中去
                batchUpsertUrl(mapInfoList, server);
            }

            //设置当前容器的url信息
            mapInfoList.clear();
            mapInfoList = getCurUrl();
            batchUpsertUrl(mapInfoList, "link-base");

        } catch (Exception e) {
            logger.error(e.getMessage());
            throw new Exception(e);
        }
    }

    /**
     * @author HuangLJ
     * @date 2019-07-28 17:09
     * @param mapInfoList 容器中的url信息
     * @param moduleName 模块名称
     * @throws Exception 报错
     * 批量插更新url信息。基于模块来
     */
    private void batchUpsertUrl(Map<String, UrlInfo> mapInfoList, String moduleName) throws Exception {
        //去重
        //比较当前数据库和查询出来的差异
        //查询出当前的
        UrlInfo example = new UrlInfo();
        example.setPageFlag(false);
        example.setModuleName(moduleName);
        List<UrlInfo> list = urlInfoMapper.queryByExamplePage(example);

        //先删除数据库中多余的
        deleteExtraUrl(mapInfoList, list);

        //插入没有的
        batchInsertSub(list, moduleName, mapInfoList);
//        int size = mapInfoList.size();
//        if (size > 300) {
//            //如果url列表大于300，则启动多线程来处理。目前用30个线程跑
//
//            //首先要删除多余的
//        }
    }

    /**
     * @author HuangLJ
     * @date 2019-07-28 16:17
     * @param mapInfoList 指定模块中，容器中的URL信息
     * @param list 指定模块中，数据库中的URL信息
     * @throws Exception 报错
     * 删除数据库中多余的url
     */
    private void deleteExtraUrl(Map<String, UrlInfo> mapInfoList, List<UrlInfo> list) throws Exception {
        //比较差异
        //需要删除的
        List<UrlInfo> urlInfoDelete = getDeleteUrl(mapInfoList, list);
        for (UrlInfo urlInfo : urlInfoDelete) {
            //删除数据库中
            UrlInfo urlInfoDel = new UrlInfo();
            urlInfoDel.setId(urlInfo.getId());
            urlInfoMapper.deleteById(urlInfoDel);
        }
    }

    /**
     * @author  HuangLJ
     * @param map  工程中所有的URL 的map
     * @param list 数据库中所有的URL
     * @return 需要删除的URL
     * @throws ServiceException 异常信息
     * 获取需要删除的URL
     */
    private List<UrlInfo> getDeleteUrl(Map<String, UrlInfo> map, List<UrlInfo> list) throws ServiceException {
        List<UrlInfo> urlDelete = new ArrayList<>();
        for (UrlInfo urlInfo : list) {
            String url = urlInfo.getUrl();
            boolean existFlag = false;
            for (Map.Entry<String, UrlInfo> urlEntry : map.entrySet()) {
                if (url.equals(urlEntry.getKey())) {
                    existFlag = true;
                    break;
                }
            }
            if (!existFlag) {
                urlDelete.add(urlInfo);
            }
        }

        return urlDelete;
    }

    /**
     * @author HuangLJ
     * @date 2019-07-28 17:02
     * 获取当前容器中的url信息
     */
    private Map<String, UrlInfo> getCurUrl() throws Exception {
        Map<RequestMappingInfo, HandlerMethod> mapUrl = requestMappingHandlerMapping.getHandlerMethods();
        Map<String, UrlInfo> map = new HashMap<>();
        for (RequestMappingInfo info : mapUrl.keySet()) {
            //获取得到url信息
            String url = info.getPatternsCondition().toString();
            url = url.replace("[", "").replace("]", "");
            if (url.startsWith("/loyalty/")) {
                //排除会员的url
                continue;
            }
            String controllerName = mapUrl.get(info).getBean().toString();
            //把url拆开。拼接起来。返回到接口调用方 格式 url|controllerName
            UrlInfo urlInfo = new UrlInfo();
            urlInfo.setUrl(url);
            urlInfo.setName(controllerName);
            urlInfo.setModuleName("link-base");
            map.put(url, urlInfo);
        }

        return map;
    }

    @Override
    public UrlInfo queryByUrl(String url) {
        if (StringUtils.isBlank(url)) {
            return  null;
        }
        return urlInfoMapper.queryByUrl(url);
    }

    @Override
    public List<UrlInfo> queryUrlByUser(UrlInfo example) {

        if (StringUtils.isBlank(example.getAttr1())) {
            throw new ServiceException("PUBLIC-016");
        }
        return urlInfoMapper.queryUrlByUser(example);
    }


    /**
     * @author HuangLJ 2018年09月25日
     * @param urlInfoList 数据库中的List对象
     * @param moduleName 模块名称
     * @param map 容器中的url信息
     * @throws Exception 报错
     */
    @Override
    public void batchInsertSub(List<UrlInfo> urlInfoList, String moduleName, Map<String, UrlInfo> map) throws Exception {
        //需要插入的
        List<UrlInfo> urlInfoInsert = getLessUrl(urlInfoList, map);

        //获取当前登录人信息
        CoreUser user = UserUtil.getUser();
        Long userId = user.getId();
        Long userPostId = user.getPostnId();
        Long orgId = user.getOrgId();
        Long corpId = user.getLoginCorpId();
        JSONObject jsonObject = new JSONObject();
        //设置默认crm可访问
        jsonObject.put("crm", "Y");
        //设置默认dms可访问
        jsonObject.put("dms", "Y");
        String scope = JSON.toJSONString(jsonObject);
        //因为自己写的方法，不走拦截器，所以需要自己完善其他信息。
        for (UrlInfo urlInfo : urlInfoInsert) {
            urlInfo.setCreatedBy(userId);
            urlInfo.setLastUpdatedBy(userId);
            urlInfo.setPostnId(userPostId);
            urlInfo.setOrgId(orgId);
            urlInfo.setId(keyGenerateService.keyGenerate());
            urlInfo.setLoginCorpId(corpId);
            urlInfo.setCorpid(corpId);
            urlInfo.setScopePermission(scope);
            urlInfo.setDefaultAccessFlag("N");
            urlInfo.setDelFlag("N");
        }
        if (urlInfoInsert.size() > 0) {
            urlInfoMapper.batchInsertSub(urlInfoInsert);
        }
    }

    /**
     * @author HuangLJ
     * @date 2018/9/27 下午1:59
     * @param list 数据库中的url列表
     * @param mapInfoList 容器中的url列表
     * @return mapInfoList在list中多余的部分
     * @throws ServiceException 异常信息
     * 比较list 和mapInfoList 把mapInfoList在list中多余的部分留下
     */
    private List<UrlInfo> getLessUrl(List<UrlInfo> list, Map<String, UrlInfo> mapInfoList) throws ServiceException {

        List<UrlInfo> urlLessInfoList = new ArrayList<>();
        for (Map.Entry<String, UrlInfo> entry : mapInfoList.entrySet()) {
            boolean existFlag = false;
            String url = entry.getKey();
            for (UrlInfo urlInfo : list) {
                if (url.equals(urlInfo.getUrl())) {
                    existFlag = true;
                    break;
                }
            }
            if (!existFlag) {
                UrlInfo urlInfoRet = entry.getValue();
                urlLessInfoList.add(urlInfoRet);
            }
        }
        return urlLessInfoList;
    }


    /**
     * 加密url
     *
     * @param urlEncrypt 加密对象
     * @return 加密的字符串
     * @throws Exception 异常信息
     * @author LiQun
     * @date 2018/12/13
     */
    @Override
    public String encryptUrl(UrlEncrypt urlEncrypt) throws Exception {
        String key = "DHIm6nNgKmxCLENT+nlOSA==";
        String iv = "XF39t6F2eJUNJ2HL1mQgRw==";
        LinkRedisClient j = RedisUtil.getJedis();
        String encryptData = null;
        Long id = null;
        try {
            // 需要被加密的字符串
            String params = urlEncrypt.getParams();
            byte[] keyBytes = Base64.decodeBase64(key);
            byte[] ivBytes = Base64.decodeBase64(iv);
            byte[] contentBytes = params.getBytes();

            // 加密字符串
            LCLogger.info().withMessageKey("encryptUrl")
                    .withMessage("加密前的内容 : " + params).flush();

            LCLogger.info().withMessageKey("encryptUrl")
                    .withMessage("加密密钥 : " + Base64.encodeBase64String(keyBytes)).flush();

            // 加密方法
            byte[] enc = PasswordUtil.encrypt(contentBytes, keyBytes, ivBytes);
            encryptData = Base64.encodeBase64String(enc);

            LCLogger.info().withMessageKey("encryptUrl")
                    .withMessage("加密后的内容 : " + encryptData).flush();

            // 生成id,返回给前端
            id = keyGenerateService.keyGenerate();
            // 将参数写入到redis中
            j.hset("SHARE_URL_ENCRYPT_DATA:" + id, String.valueOf(id), encryptData);
        } catch (Exception e) {
            LCLogger.withException(e);

        } finally {
            RedisUtil.returnResource(j);
        }
        return String.valueOf(id);
    }

    /**
     * 解密url
     *
     * @param urlEncrypt 加密对象
     * @return 解密的字符串
     * @throws Exception 异常信息
     * @author LiQun
     * @date 2018/12/13
     */
    @Override
    public String decryptUrl(UrlEncrypt urlEncrypt) throws Exception {
        String key = "DHIm6nNgKmxCLENT+nlOSA==";
        String iv = "XF39t6F2eJUNJ2HL1mQgRw==";
        String urlParams = null;
        LinkRedisClient j = RedisUtil.getJedis();
        String id = null;
        try {
            byte[] keyBytes = Base64.decodeBase64(key);
            byte[] ivBytes = Base64.decodeBase64(iv);
            String encryptData = j.hget("SHARE_URL_ENCRYPT_DATA:" + urlEncrypt.getParams(), urlEncrypt.getParams());
            if (encryptData == null) {
                throw new ServiceException("URIINFO-005");
            }
            byte[] encryptDataBytes = Base64.decodeBase64(encryptData);
            // 解密方法
            byte[] dec = PasswordUtil.decrypt(encryptDataBytes, keyBytes, ivBytes);
            urlParams = new String(dec);

            LCLogger.info().withMessageKey("decryptUrl")
                    .withMessage("解密后的内容 : " + urlParams).flush();
            if (!urlParams.equals(encryptData)) {
                throw new ServiceException("URIINFO-004");
            }
        } catch (Exception e) {
            LCLogger.withException(e);

        } finally {
            RedisUtil.returnResource(j);
        }
        return urlParams;
    }

    /**
     * 加密url
     *
     * @param urlParam 包含参数的url
     * @return 加密的字符串
     * @throws Exception 异常信息
     * @author LiQun
     * @date 2018/12/13
     */
    @Override
    public Map<String, Object> getUrlParams(String urlParam) throws Exception {
        Map<String, Object> result = new HashMap<>(16);
        String[] paramArray = urlParam.split("&");
        for (String paramPair : paramArray) {
            String[] pair = paramPair.split("=");
            if (pair.length == 2) {
                result.put(pair[0], pair[1]);
            }
        }
        return result;
    }
}
